﻿using System.Text;
using System.Text.RegularExpressions;
using DimensionsHelper.Common.Metadata.Axis;

namespace DimensionsHelper.CommandLine.PreTableRender;

/// <summary>
///     用于执行mrScriptCL脚本并生成html格式的数据表，读取表格的分类元素顺序，
///     最后使用各个表格配置名替换文件的占位符，并生成目标文件。
/// </summary>
/// <remarks>
///     占位符规则如下，其中，opt表示配置名称，lb表示下限，rb表示上限。
///     上限和下限均是0开始。
///     <list type="bullet">
///         <item><term>${opt}</term>替换为完整的轴表达式，带有描述和花括号。</item>
///         <item><term>${opt:Code}</term>替换为完整的表侧码号，不带有描述。</item>
///         <item><term>${opt:Code:lb}</term>替换为排序位置在lb的分类元素。</item>
///         <item><term>${opt:Code:lb-}</term>筛选从lb开始的所有分类元素码号列表后，再替换占位符。</item>
///         <item><term>${opt:Code:lb-rb}</term>筛选从lb开始到rb结束（闭区间）的码号列表后，再替换占位符。</item>
///         <item><term>${opt:Label:lb}</term>替换为排序位置在lb的分类元素描述。</item>
///     </list>
/// </remarks>
public partial class PreTableExecutor
{
    private readonly Dictionary<string, AxisExpression> _axes;
    private readonly Encoding _encoding;

    private readonly PreTableOptions _options;
    private readonly Dictionary<string, string> _replacements;


    static PreTableExecutor()
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
    }


    private PreTableExecutor(PreTableOptions options)
    {
        _options = options;
        _encoding = Encoding.GetEncoding(options.Encoding);
        _replacements = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        _axes = new Dictionary<string, AxisExpression>(StringComparer.OrdinalIgnoreCase);
    }


    private static string GetElementCodeReplacement(string name, bool numberCode)
    {
        if (numberCode && name.Length > 0 && char.IsDigit(name[^1]))
        {
            int p = name.Length - 1;

            while (char.IsDigit(name[p]))
            {
                p--;
            }

            return name[(p + 1)..];
        }

        return name;
    }


    private static void RemoveCategoryDescription(AxisElement element)
    {
        if (element.Type == AxisElementType.Category)
        {
            element.Description = null;
        }
        else
        {
            foreach (AxisElement item in element.Elements)
            {
                RemoveCategoryDescription(item);
            }
        }
    }


    private void LoadTableInfo(TableInfo tableInfo)
    {
        PreFieldOptions? fieldOptions = _options.Fields.FirstOrDefault(o => o.Name.Equals(tableInfo.OptionName));

        if (fieldOptions == null)
        {
            return;
        }

        int count = 0;
        var builder = new StringBuilder();

        foreach (AxisElement element in tableInfo.Axis)
        {
            if (string.IsNullOrEmpty(element.Name))
            {
                continue;
            }

            if (element.Type == AxisElementType.Category ||
                element.Type == AxisElementType.Net ||
                element.Type == AxisElementType.Combine)
            {
                string code;
                if (element.Type == AxisElementType.Net || element.Type == AxisElementType.Combine)
                {
                    RemoveCategoryDescription(element);
                    code = element.ToString();
                }
                else
                {
                    code = GetElementCodeReplacement(element.Name, fieldOptions.NumberCode);
                }

                if (builder.Length > 0)
                {
                    builder.Append(',');
                }

                builder.Append(code);

                _replacements.TryAdd($"${{{tableInfo.OptionName}:Code:{count}}}", code);
                _replacements.TryAdd($"${{{tableInfo.OptionName}:Label:{count}}}", element.Description ?? "");
                count++;
            }
        }

        if (builder.Length > 0)
        {
            _replacements.TryAdd($"${{{tableInfo.OptionName}:Code}}", builder.ToString());
        }

        _replacements.TryAdd($"${{{tableInfo.OptionName}}}", tableInfo.Axis.ToString());
        _axes.TryAdd(fieldOptions.Name, tableInfo.Axis);
    }


    private void InitializeReplacement(List<TableInfo> tables)
    {
        _replacements.Clear();

        foreach (TableInfo tab in tables)
        {
            LoadTableInfo(tab);
        }
    }


    private List<TableInfo> PopulateHtmlTable()
    {
        var htmlRender = new HtmlTableRender(_options);
        htmlRender.Apply();
        List<TableInfo> result = HtmlTableReader.ReadHtmlTable(htmlRender.HtmlName);

        if (!_options.Keep)
        {
            htmlRender.ClearTempFiles();
        }

        return result;
    }


    [GeneratedRegex(@"\$\{(?<opt>[^:]+?)(?:(?:\:Code(\:(?<lb>\d+)(?:\-(?<rb>\d+)?)?)?)|(?:\:Label\:\d+))?\}",
        RegexOptions.IgnoreCase, "zh-CN")]
    private static partial Regex GetPlaceHolderRegex();


    private string ReplaceText(string content)
    {
        return GetPlaceHolderRegex().Replace(content, ReplaceMatchEvaluator);
    }


    private string GetMatchRangeElements(string optName, int lb, int rb)
    {
        if (_axes.TryGetValue(optName, out AxisExpression? axis))
        {
            var builder = new StringBuilder();
            int c = 0;
            foreach (AxisElement item in axis)
            {
                if (item.Type == AxisElementType.Category ||
                    item.Type == AxisElementType.Combine ||
                    item.Type == AxisElementType.Net)
                {
                    if (rb >= 0 && c > rb)
                    {
                        break;
                    }

                    if (c >= lb)
                    {
                        if (builder.Length > 0)
                        {
                            builder.Append(", ");
                        }

                        if (item.Type == AxisElementType.Category)
                        {
                            builder.Append(item.Name);
                        }
                        else
                        {
                            builder.Append(item);
                        }
                    }

                    c++;
                }
            }

            return builder.ToString();
        }

        return string.Empty;
    }


    private string ReplaceMatchEvaluator(Match match)
    {
        if (_replacements.TryGetValue(match.Value, out string? str))
        {
            return str;
        }

        string optName = match.Groups["opt"].Value;
        string lb = match.Groups["lb"].Value;
        string rb = match.Groups["rb"].Value;

        if (!string.IsNullOrEmpty(optName) && int.TryParse(lb, out int lbv))
        {
            if (!int.TryParse(rb, out int rbv))
            {
                rbv = -1;
            }

            return GetMatchRangeElements(optName, lbv, rbv);
        }

        return match.Value;
    }


    private void ReplaceFile(string source, string target)
    {
        string text = File.ReadAllText(source, _encoding);
        string result = ReplaceText(text);

        using StreamWriter targetFile = File.CreateText(target);
        targetFile.WriteLine(result);
        targetFile.Close();
    }


    private void Execute()
    {
        List<TableInfo> tabs = PopulateHtmlTable();
        InitializeReplacement(tabs);

        foreach ((string source, string target) in _options.Files)
        {
            ReplaceFile(_options.AsAbsolutePath(source), _options.AsAbsolutePath(target));
        }
    }


    /// <summary>
    ///     执行 html 表格渲染并替换模板文件内容
    /// </summary>
    public static void Execute(PreTableOptions options)
    {
        var executor = new PreTableExecutor(options);
        executor.Execute();
    }
}